#include "Util.hpp"
#include "Notification.hpp"
#include <dirent.h>
#include <sys/unistd.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <sstream>
#include <cpp3ds/System/FileSystem.hpp>
#include <cpp3ds/System/FileInputStream.hpp>
#include <cpp3ds/System/I18n.hpp>
#include <inttypes.h>
#include <fstream>

namespace Clock
{

bool pathExists(const char* path, bool escape)
{
	struct stat buffer;
	if (escape)
		return stat(cpp3ds::FileSystem::getFilePath(path).c_str(), &buffer) == 0;
	else
		return stat(path, &buffer) == 0;
}

bool fileExists (const std::string& name) {
	struct stat buffer;
	return (stat (name.c_str(), &buffer) == 0);
}

void makeDirectory(const char *dir, mode_t mode)
{
	char tmp[256];
	char *p = NULL;
	size_t len;

	snprintf(tmp, sizeof(tmp),"%s",dir);
	len = strlen(tmp);
	if(tmp[len - 1] == '/')
		tmp[len - 1] = 0;
	for(p = tmp + 1; *p; p++)
		if(*p == '/') {
			*p = 0;
			mkdir(tmp, mode);
			*p = '/';
		}
	mkdir(tmp, mode);
}

int removeDirectory(const char *path, bool onlyIfEmpty)
{
	DIR *d = opendir(path);
	size_t path_len = strlen(path);
	int r = -1;

	if (d) {
		struct dirent *p;
		r = 0;

		while (!r && (p = readdir(d))) {
			int r2 = -1;
			char *buf;
			size_t len;

			/* Skip the names "." and ".." as we don't want to recurse on them. */
			if (!strcmp(p->d_name, ".") || !strcmp(p->d_name, ".."))
				continue;

			len = path_len + strlen(p->d_name) + 2;
			buf = (char*) malloc(len);

			if (buf) {
				struct stat sb;
				snprintf(buf, len, "%s/%s", path, p->d_name);

				if (!stat(buf, &sb)) {
					if (S_ISDIR(sb.st_mode))
						r2 = removeDirectory(buf, onlyIfEmpty);
					else if (!onlyIfEmpty)
						r2 = unlink(buf);
				}
				free(buf);
			}
			r = r2;
		}
		closedir(d);
	}
	if (!r)
		r = rmdir(path);

	return r;
}

void hexToRGB(std::string hexValue, int *R, int *G, int *B)
{
	std::string strR, strG, strB;

	strR = hexValue.substr(0, 2);
	strG = hexValue.substr(2, 2);
	strB = hexValue.substr(4, 2);

	std::istringstream(strR) >> std::hex >> *R;
	std::istringstream(strG) >> std::hex >> *G;
	std::istringstream(strB) >> std::hex >> *B;
}

std::string ReplaceAll(std::string str, const std::string& from, const std::string& to) {
	size_t start_pos = 0;
	while((start_pos = str.find(from, start_pos)) != std::string::npos) {
		str.replace(start_pos, from.length(), to);
		start_pos += to.length(); // Handles case where 'to' is a substring of 'from'
	}
	return str;
}

cpp3ds::String getUsername()
{
#ifndef EMULATION
	if (R_SUCCEEDED(cfguInit())) {
		u16 *userOut = static_cast<u16*>(malloc(0x1C));
		CFGU_GetConfigInfoBlk2(0x1C, 0xA0000, (u8*)userOut);

		cfguExit();

		cpp3ds::String username = cpp3ds::String::fromUtf16(userOut, userOut + 0x1C);
		free(userOut);

		return username;
	}
#else
	return "emu3ds";
#endif
}

cpp3ds::Uint16 ModRTU_CRC(const void *buf, size_t len)
{
	const cpp3ds::Uint8* ubuf = (const cpp3ds::Uint8*)buf;
	cpp3ds::Uint16 crc = 0xFFFF;

	for (int pos = 0; pos < len; pos++) {
		crc ^= (cpp3ds::Uint16)ubuf[pos];          // XOR byte into least sig. byte of crc

		for (int i = 8; i != 0; i--) {    // Loop over each bit
			if ((crc & 0x0001) != 0) {      // If the LSB is set
				crc >>= 1;                    // Shift right and XOR 0xA001
				crc ^= 0xA001;
			}
			else                            // Else LSB is not set
			crc >>= 1;                    // Just shift right
		}
	}
	// Note, this number has low and high bytes swapped, so use it accordingly (or swap bytes)
	return crc;
}

// https://github.com/devkitPro/libnds/blob/master/source/arm7/userSettings.c
cpp3ds::Uint32 getNVRAMActiveBlock()
{
#ifndef EMULATION
	Result res;

	if (R_FAILED(res = cfgnorInit(1))) {
		Notification::spawn(_("Unable to start CFGNOR: \n0x%08lX (%d)", res, R_DESCRIPTION(res)));
		return 0x0;
	}

	if (R_FAILED(res = CFGNOR_Initialize(1))) {
		Notification::spawn(_("Unable to init CFGNOR: \n0x%08lX (%d)", res, R_DESCRIPTION(res)));
		return 0x0;
	}

	u16 *userSettingsBase = (u16*)malloc(0x2 * sizeof(u16));

	res = CFGNOR_ReadData(0x20, (u32*)userSettingsBase, 0x2);

	//printf("userSettingsBase: 0x%08lX\n", *userSettingsBase);

	cpp3ds::Uint32 slot1Address = *userSettingsBase * 8;
	cpp3ds::Uint32 slot2Address = *userSettingsBase * 8 + 0x100;

	//printf("addrSlot1: 0x%08lX, addrSlot2: 0x%08lX\n", slot1Address, slot2Address);

	u16 *slot1Count = (u16*)malloc(0x2 * sizeof(u16));
	u16 *slot2Count = (u16*)malloc(0x2 * sizeof(u16));
	u16 *slot1CRC = (u16*)malloc(0x2 * sizeof(u16));
	u16 *slot2CRC = (u16*)malloc(0x2 * sizeof(u16));

	res = CFGNOR_ReadData(slot1Address + 0x70, (u32*)slot1Count, 0x2);
	res = CFGNOR_ReadData(slot2Address + 0x70, (u32*)slot2Count, 0x2);
	res = CFGNOR_ReadData(slot1Address + 0x72, (u32*)slot1CRC, 0x2);
	res = CFGNOR_ReadData(slot2Address + 0x72, (u32*)slot2CRC, 0x2);

	//printf("Slot 1: cnt 0x%08lX crc 0x%08lX\nSlot 2: cnt 0x%08lX crc 0x%08lX\n", *slot1Count, *slot1CRC, *slot2Count, *slot2CRC);

	u16 *slot1Data = (u16*)malloc(0x70 * sizeof(u16));
	u16 *slot2Data = (u16*)malloc(0x70 * sizeof(u16));

	res = CFGNOR_ReadData(slot1Address, (u32*)slot1Data, 0x70);
	res = CFGNOR_ReadData(slot2Address, (u32*)slot2Data, 0x70);

	cpp3ds::Uint16 slot1ComputedCRC = ModRTU_CRC(slot1Data, 0x70);
	cpp3ds::Uint16 slot2ComputedCRC = ModRTU_CRC(slot2Data, 0x70);

	//printf("CRC data1: 0x%08lX\nCRC data2: 0x%08lX\n", slot1ComputedCRC, slot2ComputedCRC);

	if (*slot1CRC != slot1ComputedCRC && *slot2CRC != slot2ComputedCRC) {
		//printf("Oooops, CRC doesn't match\n");
		return 0x0;
	}

	cpp3ds::Uint32 activeAddress = slot1Address;

	if (*slot1CRC == slot1ComputedCRC && *slot2CRC == slot2ComputedCRC) {
		//printf("Both CRC matches !\n");
		activeAddress = (*slot2Count == ((*slot1Count + 1) & 0x7f) ? slot2Address : slot1Address);
	} else {
		if (*slot2CRC == slot2ComputedCRC)
			activeAddress = slot2Address;
	}

	//printf("Chosen address: 0x%08lX\n", activeAddress);

	if (R_FAILED(res = CFGNOR_Shutdown()))
		Notification::spawn(_("Unable to shutdown CFGNOR: \n0x%08lX (%d)", res, R_DESCRIPTION(res)));

	cfgnorExit();

	return activeAddress;
#endif
}

cpp3ds::Color getNVRAMTheme(cpp3ds::Uint32 activeBlockAddress)
{
#ifndef EMULATION
	cpp3ds::Uint32 themeAddress = activeBlockAddress + 0x2;
	cpp3ds::Color themeColor = cpp3ds::Color::Transparent;
	cpp3ds::Color themeColors[] = {cpp3ds::Color(97, 130, 154), cpp3ds::Color(186, 73, 0), cpp3ds::Color(251, 0, 24), cpp3ds::Color(251, 138, 251), cpp3ds::Color(251, 146, 0), cpp3ds::Color(243, 227, 0), cpp3ds::Color(170, 251, 0), cpp3ds::Color(0, 251, 0), cpp3ds::Color(0, 162, 56), cpp3ds::Color(73, 219, 138), cpp3ds::Color(48, 186, 243), cpp3ds::Color(0, 89, 243), cpp3ds::Color(0, 0, 146), cpp3ds::Color(138, 0, 211), cpp3ds::Color(211, 0, 235), cpp3ds::Color(251, 0, 146)};

	Result res;

	if (R_FAILED(res = cfgnorInit(1))) {
		Notification::spawn(_("Unable to start CFGNOR: \n0x%08lX (%d)", res, R_DESCRIPTION(res)));
		return themeColor;
	}

	if (R_FAILED(res = CFGNOR_Initialize(1))) {
		Notification::spawn(_("Unable to init CFGNOR: \n0x%08lX (%d)", res, R_DESCRIPTION(res)));
		return themeColor;
	}

	u8 *themeInfo = (u8*)malloc(0x2 * sizeof(u8));
	res = CFGNOR_ReadData(themeAddress, (u32*)themeInfo, 0x2);

	//printf("Got theme: 0x%08lX\n", *themeInfo);
	themeColor = themeColors[*themeInfo];

	if (R_FAILED(res = CFGNOR_Shutdown()))
		Notification::spawn(_("Unable to shutdown CFGNOR: \n0x%08lX (%d)", res, R_DESCRIPTION(res)));

	cfgnorExit();

	return themeColor;
#else
	return cpp3ds::Color::Red;
#endif
}

int getDaysInMonth(int month, int year)
{
	if (month == 4 || month == 6 || month == 9 || month == 11)
		return 30;

	bool isLeapYear = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);

	if (month == 2) {
		if (isLeapYear)
			return 29;
		else
			return 28;
	}

	return 31;
}

void getBirthday(int *day, int *month)
{
#ifndef EMULATION
if (R_SUCCEEDED(cfguInit())) {
	u16 *birthOut = static_cast<u16*>(malloc(0x2));
	CFGU_GetConfigInfoBlk2(0x2, 0xA0001, (u8*)birthOut);

	cfguExit();

	u8 birthday[0x2];
	memcpy(&birthday, birthOut, 0x2);

	free(birthOut);

	*day = birthday[1];
	*month = birthday[0];
}
#else
	*day = 2;
	*month = 8;
#endif
}

} // namespace Clock
